﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;
using NATUPNPLib;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

namespace GShare1._0.Server
{
    public delegate void UiThreadInvoker(MethodInvoker del);
    public delegate void MessageRelayDelegate(string msg);
    public class Server
    {
        
        public ServerRoom ServerRoom = new ServerRoom();
        public MainThreadInvoker MainInvoker;
        public Socket ServerSocket;
        public Socket DownloadSocket;
        public List<byte> ClientInfoBuffer = new List<byte>();
        public List<ClientInfo> ConnectedClientInfoList = new List<ClientInfo>();
        public List<Client> ClientList = new List<Client>();
        public MessageBoxInvoker Invoker;
        private ServerState _state;
        
        private Timer ticker = new Timer();
        public ClientListUpdateDelegate UpdateListInvoker;
        private bool _isOnline;
        public bool IsOnline
        {
            get { return _isOnline; }
            set { _isOnline = value; }
        }
        private MessageRelayDelegate messageRelayInvoker;
        private UPnPNATClass nat;

        private string _password;

        public string Password
        {
            get { return _password; }
            set { _password = value; }
        }
        

        public Server(MessageBoxInvoker invoker,ClientListUpdateDelegate listInvoker,MainThreadInvoker mainInvoker)
        {
            Invoker = invoker;
            MainInvoker = mainInvoker;
            UpdateListInvoker = listInvoker;
            messageRelayInvoker = Message;
            _isOnline = false;
            nat = new UPnPNATClass();
        }

        internal void Start()
        {
            try
            {
                //Create Rooms and create list view for connections
                UpdateListInvoker(ClientList);
                

                //Socket creation,binding and listen mode
                ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                ServerSocket.Bind(new IPEndPoint(0,3333));
                ServerSocket.Listen(0);
                

                //begin to accept connections from clients + downloads/uploads
                Invoker.BeginInvoke("Server Started", null, null);
                
///PROBLEM WITH SOCKET BEING BOUND

                ServerSocket.BeginAccept(ServerSocket.ReceiveBufferSize,new AsyncCallback(AcceptCallback), null);
                //_downloadSocket.BeginAccept(_downloadSocket.ReceiveBufferSize, new AsyncCallback(DataTransferCallback), null);
                IStaticPortMappingCollection mappings = nat.StaticPortMappingCollection;
                mappings.Add(3333, "TCP", 3333, IpAddressHelper.LocalIpAddress(), true, "Gshare Server");
                //mappings.Add(3334, "TCP", 3334, LocalIPAddress(), true, "Gshare Download");
                _isOnline = true;

                




            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + ex.TargetSite);

            }
        }

        /// <summary>
        /// NOT USED SINCE SERVER HANDELS DOWNLOADS WITHIN THE ACCEPT CALLBACK
        /// </summary>
        /// <param name="ar"></param>
        private void DataTransferCallback(IAsyncResult ar)
        {
            try
            {
                if (_isOnline)
                {
                    byte[] buffer = new byte[DownloadSocket.ReceiveBufferSize];
                    //First Get Name of DataTransfer client
                    _state = new ServerState();
                    _state.Client = DownloadSocket.EndAccept(out buffer, ar);
                    string strmsg = Encoding.ASCII.GetString(buffer);
                    string[] str = strmsg.Split(',');


                    //Find match in the client list
                    foreach (Client client in ClientList)
                    {
                        if (client.ClientInfo.UserName == str[0])
                        {
                            client.Download = new Download(client.ClientInfo.UserName, MainInvoker, str[1]);


                            MainInvoker.Invoke((MethodInvoker)delegate
                            {
                                client.Download.BeginConnection(_state.Client);
                            });

                            //delete the download object when the finish is called
                            client.Download.Finish += download_Finish;

                        }
                    }

                    DownloadSocket.BeginAccept(DownloadSocket.ReceiveBufferSize, new AsyncCallback(DataTransferCallback), null);

                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            
        }

        void download_Finish(object sender, EventArgs e)
        {
           
        }

        private void AcceptCallback(IAsyncResult ar)
        {
            try
            {
                if (_isOnline)
                {

                    _state = new ServerState();
                    _state.Client = ServerSocket;
                    ClientInfo info = new ClientInfo();
                    
                    //Create Client from the IAsyncResult (ar)
                    Client client;
                    byte[] buffer = new byte[ServerSocket.ReceiveBufferSize];
                    Socket tempSocket = ServerSocket.EndAccept(out buffer, ar);
                    
                    //Buffer has been read we now check to see if it is a download/upload connection or a general server connection
                    string bufferString = Encoding.ASCII.GetString(buffer);
                    string[] bufferSplit = bufferString.Split(',');
                    switch (bufferSplit[0])
                    {
                        case "CONNECT":
                            client = new Client(tempSocket, Invoker, info, this, messageRelayInvoker, MainInvoker);
                            //Check if the username is already in use
                            bool usernameCheck = false;

                            if (ClientList.Count != 0)
                            {
                                foreach (Client item in ClientList.ToArray())
                                {
                                    //check if they match
                                    if (bufferSplit[1] == item.ClientInfo.UserName)
                                    {
                                        byte[] sendBuffer = Encoding.ASCII.GetBytes("CONNECT_FAIL,USERNAME_TAKEN");
                                        tempSocket.BeginSend(sendBuffer, 0, sendBuffer.Length, SocketFlags.None, null, null);

                                        usernameCheck = true;
                                        break;
                                        
                                    }
                                    
                                }
                                if (usernameCheck == false)
                                {

                                        byte[] sendBuffer = Encoding.ASCII.GetBytes("CONNECT_SUCCESS");
                                        tempSocket.BeginSend(sendBuffer, 0, sendBuffer.Length, SocketFlags.None, null, null);
                                        
                                }

                            }
                            else
                            {
                                byte[] sendBuffer = Encoding.ASCII.GetBytes("CONNECT_SUCCESS");
                                tempSocket.BeginSend(sendBuffer, 0, sendBuffer.Length, SocketFlags.None, null, null);
                                usernameCheck = false;
                            }

                            if (usernameCheck == false)
                            {
                                //Client Info
                                client.ClientInfo.UserName = bufferSplit[1];
                                client.ClientInfo.ConnectionTime = DateTime.Now;
                                client.ClientInfo.IpAddress = tempSocket.RemoteEndPoint.ToString();


                                //Message Delegates
                                Invoker.BeginInvoke(client.ClientInfo.UserName + " Connected", null, null);
                                messageRelayInvoker.BeginInvoke(client.ClientInfo.UserName + " Connected", null, null);

                                //Restart Receive Loop
                                client.ClientSocket.BeginReceive(client.Buffer, 0, client.Buffer.Length, SocketFlags.None, new AsyncCallback(client.ReceiveCallback), _state);

                                //List Append
                                ClientList.Add(client);
                                ServerRoom.ClientList.Add(client);
                                ConnectedClientInfoList.Add(client.ClientInfo);

                                //Update the List on server side UI
                                UpdateListInvoker.BeginInvoke(ClientList, null, null);

                                //Send Client Info to all connected Users now that new connection has been made
                                SendClientInfo();
                            }
                            

                            break;
                        case "DOWNLOAD":
                            //create download socket
                            //Find match in the client list
                            foreach (Client clientItem in ClientList)
                            {
                                if (clientItem.ClientInfo.UserName == bufferSplit[1])
                                {
                                    clientItem.Download = new Download(clientItem.ClientInfo.UserName, MainInvoker, bufferSplit[2]);


                                    MainInvoker.Invoke((MethodInvoker)delegate
                                    {
                                        clientItem.Download.BeginConnection(tempSocket);
                                    });

                                    //delete the download object when the finish is called
                                    clientItem.Download.Finish += download_Finish;

                                }
                            }
                            break;
                        case "UPLOAD":
                            break;
                        case "UPDATELIST":
                            //Find match in the client list
                            foreach (Client clientItem in ClientList)
                            {
                                if (clientItem.ClientInfo.UserName == bufferSplit[1])
                                {
                                    clientItem.BeginListUpdate(tempSocket);
                                }
                            }
                            break;
                        default:
                            break;
                    }
                    //add password SYSTEM HERE
                    
                    
                    

                    //using the client socket start receiving
                    //Here all client operations get handeld inside client
                    
                    //add client to the connected list

                    


                    //Create Async loop to keep handling incomming connections
                    ServerSocket.BeginAccept(ServerSocket.ReceiveBufferSize,new AsyncCallback(AcceptCallback), null);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        internal void Shutdown()
        {
            if (_isOnline)
            {
                ticker.Stop();
                _isOnline = false;
                if (ClientList.Count != 0)
                {
                    foreach (Client client in ClientList.ToList())
                    {
                        if (client.Download != null)
                        {

                        }
                        client.Disconnect();
                        
                    }
                    //Remove the ConnectedClientInfo List
                    ConnectedClientInfoList.Clear();


                }
                //call syncronously
                Invoker("Server Stopped");


                ClientList.Clear();
                ServerSocket.Close();

                //Remove the UPNP forward
                IStaticPortMappingCollection mappings = nat.StaticPortMappingCollection;
                try
                {
                    mappings.Remove(3333, "TCP");
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message + ex.TargetSite);
                }
            }
            
            
        }

        internal void Message(string msg)
        {
            foreach (Client client in ClientList.ToList())
            {
                MessageSender.SendText(msg, client.ClientSocket);
            }
        }

        internal void SendClientInfo()
        {
            ClientInfoBuffer.Clear();
            BinaryFormatter BF = new BinaryFormatter();
            
                
                //add the numebr of objects to be sent
                byte[] numberOfObjects = BitConverter.GetBytes(ConnectedClientInfoList.Count);
                ClientInfoBuffer.AddRange(numberOfObjects);

                //Serialize the list of clientInfo objects into the buffer
                foreach (ClientInfo item in ConnectedClientInfoList)
                {
                    using (MemoryStream memstream = new MemoryStream())
                    {
                    BF.Serialize(memstream, item);
                    byte[] obj = memstream.ToArray();
                    byte[] objectSize = BitConverter.GetBytes(obj.Length);

                    //add the object to the list
                    ClientInfoBuffer.AddRange(objectSize);
                    ClientInfoBuffer.AddRange(obj);
                    }
                }

                //now that we have all elements calculate length of data to be sent to client
                byte[] dataSendSize = BitConverter.GetBytes(ClientInfoBuffer.Count + 4);
                ClientInfoBuffer.InsertRange(4, dataSendSize);

                byte[] serverMessage = Encoding.ASCII.GetBytes("CLIENT_INFO_LIST_UPDATE");

                foreach (Client client in ClientList)
                {
                    if (client.UpdateSocket == null || !client.UpdateSocket.Connected) client.ClientSocket.BeginSend(serverMessage, 0, serverMessage.Length, SocketFlags.None, null, null);
                    

                }

                
                
                
            }

            
                
            
            
        }



    }
